/*
* Copyright (C) 2009-2015 Johan Nilsson <http://markupartist.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.markupartist.sthlmtraveling.utils;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.CountDownTimer;
import android.support.v4.content.ContextCompat;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
/**
* Created by johan on 4/5/14.
*/
public class LocationManager implements PlayService, LocationListener {
// Timeout for finding
private static final long FIND_LOCATION_TIME_OUT_MILLIS = 8000;
// Accepted minimum location accuracy.
private static final int ACCEPTED_LOCATION_ACCURACY_METERS = 300;
// Accepted max age of an location
private static final int ACCEPTED_LOCATION_AGE_MILLIS = 60000;
// Milliseconds per second
private static final int MILLISECONDS_PER_SECOND = 1000;
// Update frequency in seconds
public static final int UPDATE_INTERVAL_IN_SECONDS = 5;
// Update frequency in milliseconds
private static final long UPDATE_INTERVAL = MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS;
// The fastest update frequency, in seconds
private static final int FASTEST_INTERVAL_IN_SECONDS = 1;
// A fast frequency ceiling in milliseconds
private static final long FASTEST_INTERVAL = MILLISECONDS_PER_SECOND * FASTEST_INTERVAL_IN_SECONDS;
private final Context mContext;
private final GoogleApiClient mGoogleApiClient;
private final LocationRequestTimeOut mLocationRequestTimeOut;
private LocationFoundListener mLocationFoundListener;
private boolean mLocationRequested;
private boolean mHighAccuracy = true;
public LocationManager(final Context context, GoogleApiClient googleApiClient) {
mContext = context;
mGoogleApiClient = googleApiClient;
mLocationRequestTimeOut = new LocationRequestTimeOut();
}
public LocationRequest createLocationRequest() {
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setPriority(mHighAccuracy ?
LocationRequest.PRIORITY_HIGH_ACCURACY :
LocationRequest.PRIORITY_LOW_POWER);
locationRequest.setInterval(UPDATE_INTERVAL);
locationRequest.setFastestInterval(FASTEST_INTERVAL);
locationRequest.setExpirationDuration(ACCEPTED_LOCATION_AGE_MILLIS);
locationRequest.setNumUpdates(5);
return locationRequest;
}
public void setLocationListener(final LocationFoundListener listener) {
mLocationFoundListener = listener;
}
Location getLastLocation() {
if (ContextCompat.checkSelfPermission(mContext,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return null;
}
return LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
}
/**
* Is the provided {@link Location} accepted?
*
* @param location the location
* @return <code>true</code> if accepted <code>false</code> otherwise
*/
public boolean isLocationAcceptable(final Location location) {
if (location == null) {
return false;
}
if (!mHighAccuracy) {
return true;
}
long timeSinceUpdate = System.currentTimeMillis() - location.getTime();
return timeSinceUpdate <= ACCEPTED_LOCATION_AGE_MILLIS
&& location.getAccuracy() <= ACCEPTED_LOCATION_ACCURACY_METERS;
}
public void onStart() {
}
@Override
public void onStop() {
removeUpdates();
}
public void resumed() {
}
public void paused() {
}
void reportLocationFound(final Location location) {
if (mLocationFoundListener != null) {
mLocationFoundListener.onMyLocationFound(location);
}
}
public void setAccuracy(boolean high) {
mHighAccuracy = high;
}
public void requestLocation() {
if (mGoogleApiClient == null) {
return;
}
if (!mGoogleApiClient.isConnected()) {
mLocationRequested = true;
} else {
requestLocationOrDeliverCurrent();
}
}
void requestLocationOrDeliverCurrent() {
if (mGoogleApiClient == null) {
return;
}
if (ContextCompat.checkSelfPermission(mContext,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
final Location location = getLastLocation();
if (isLocationAcceptable(location)) {
onLocationChanged(location);
} else {
mLocationRequestTimeOut.start();
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, createLocationRequest(), this);
}
}
public void removeUpdates() {
if (mGoogleApiClient == null) {
return;
}
mLocationRequestTimeOut.cancel();
if (mGoogleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
}
@Override
public void onLocationChanged(Location location) {
if (isLocationAcceptable(location)) {
removeUpdates();
reportLocationFound(location);
}
}
@Override
public void onConnected() {
if (mLocationRequested) {
mLocationRequested = false;
requestLocationOrDeliverCurrent();
}
}
/**
* Used to determine when we should stop search for location changes.
*/
private class LocationRequestTimeOut extends CountDownTimer {
public LocationRequestTimeOut() {
super(FIND_LOCATION_TIME_OUT_MILLIS, 1000);
}
@Override
public void onFinish() {
reportLocationFound(getLastLocation());
// Once we have reported a location, remove the need to search for more.
removeUpdates();
}
@Override
public void onTick(long millisUntilFinished) {
// Needed by the interface.
}
}
/**
*
*/
public interface LocationFoundListener {
/**
* Called when a location is found.
*
* @param location the location, could be null
*/
void onMyLocationFound(Location location);
}
}